package Basic;

import java.util.ArrayList;
import java.util.List;

import org.rtscup.core.Resource;

import Util.Util;

/**
 * 
 * @author egg Classe que representa a visao em Tiles dos agentes
 * 
 */
public class Tiles {

	protected int width; // Largura do mapa
	protected int height; // Altura do mapa
	protected int noTilesW; // Numero de tiles onde a largura sera subdividida
	protected int noTilesH; // Numero de tiles onde a altura sera subdividida

	// As constantes que representam o tipo do obstaculo
	private final static int FREE_TILE = 0;
	public final static int SIMPLE_OBSTACLE = 1;
	public final static int DYNAMIC_OBSTACLE = 2;

	public int[] obstacles; // Vetor de inteiros que contem o tipo de
	// obstaculo

	protected int totalTiles;

	// para cada tile.

	/**
	 * 
	 * Construtor
	 * 
	 * @param width
	 * @param height
	 * @param noTilesW
	 * @param noTilesH
	 * @param obstacles
	 */
	public Tiles(int width, int height, int noTilesW, int noTilesH, Long[] obstacles) {
		this.width = width;
		this.height = height;
		this.noTilesW = noTilesW;
		this.noTilesH = noTilesH;
		this.obstacles = new int[this.noTilesW * this.noTilesH];
		this.totalTiles = this.noTilesW * this.noTilesH;

		for (int i = 0; i < obstacles.length; i++) {
			this.addObstacle(obstacles[i], SIMPLE_OBSTACLE);
		}
	}

	/**
	 * Dado um ponto p, ele retorna o ponto central do tile no qual aquele ponto
	 * esta contido
	 * 
	 * @param p
	 * @return O ponto central do tile no qual p esta contido
	 */
	public Integer getCentral(Integer p) {
		return this.getCentral(this.getTile(p));
	}

	/**
	 * Retorna o ponto central do tile passado como parametro
	 * 
	 * @param tile
	 * @return O ponto central do tile
	 */
	public int getCentral(int tile) {
		int w = tile % this.noTilesW;
		int h = (tile - w) / this.noTilesW;
		double wm = (double) w + 0.5;
		double hm = (double) h + 0.5;
		return Util.newPoint((int) (wm * (this.width / this.noTilesW)), (int) (hm * (this.height / this.noTilesH)));
	}

	/**
	 * Retorna o tile no qual o ponto p esta contido
	 * 
	 * @param p
	 * @return - O tile no qual p esta contido
	 */
	public int getTile(int point) {
		int x = 0;
		int y = 0;
		int pointX = Util.getX(point);
		int pointY = Util.getY(point);
		if (pointX >= this.width)
			x = this.width - 1;
		else
			x = pointX;
		if (pointY >= this.height)
			y = this.height - 1;
		else
			y = pointY;
		int w = x / (this.width / this.noTilesW);
		int h = y / (this.height / this.noTilesH);
		return ((w * 1) + (h * this.noTilesW));
	}

	private List<Integer> get4Neighbors(int tile) {
		List<Integer> ret = new ArrayList<Integer>();

		boolean hasUp = tile / this.noTilesW >= 1;
		boolean hasDown = tile / this.noTilesW < (this.noTilesH - 1);
		boolean hasLeft = tile % this.noTilesW != 0;
		boolean hasRight = (tile + 1) % this.noTilesW != 0;

		if (hasUp) {
			ret.add(tile - this.noTilesW);
		}
		if (hasDown) {
			ret.add(tile + this.noTilesW);
		}
		if (hasLeft) {
			ret.add(tile - 1);
		}
		if (hasRight) {
			ret.add(tile + 1);
		}
//		if (hasUp && hasLeft) {
//			ret.add(tile - this.noTilesW - 1);
//		}
//		if (hasUp && hasRight) {
//			ret.add(tile - this.noTilesW + 1);
//		}
//		if (hasDown && hasLeft) {
//			ret.add(tile + this.noTilesW - 1);
//		}
//		if (hasDown && hasRight) {
//			ret.add(tile + this.noTilesW + 1);
//		}

		return ret;
	}

	/**
	 * Retorna um vetor com todos os tiles vizinhos possiveis do tile passado
	 * como parametro
	 * 
	 * @param tile
	 * @return Todos os vizinhos possiveis (que nao sao obstaculo, nem sao fora
	 *         do mapa) do tile
	 */
	public ArrayList<Integer> getNeighborsTiles(int tile) {

		ArrayList<Integer> ret = new ArrayList<Integer>();

		// Vizinhos de cima
		if (tile / this.noTilesW >= 1) {
			if (this.obstacles[tile - (this.noTilesW)] <= Tiles.FREE_TILE)
				ret.add(tile - (this.noTilesW));
		}

		// Vizinhos do lado
		if (tile % this.noTilesW != 0 && this.obstacles[tile - 1] <= Tiles.FREE_TILE)
			ret.add(tile - 1);
		if ((tile + 1) % this.noTilesW != 0 && this.obstacles[tile + 1] <= Tiles.FREE_TILE)
			ret.add(tile + 1);

		// Vizinhos de cima
		if (tile / this.noTilesW < (this.noTilesH - 1)) {
			if (this.obstacles[tile + (this.noTilesW)] <= Tiles.FREE_TILE)
				ret.add(tile + (this.noTilesW));
		}

		return ret;
	}

	public void removeObstacle(Long so, int type) {
		int tile1 = this.getTile(Util.newPoint(Util.getX(so) - (Util.getWidth(so) / 2), Util.getY(so) - (Util.getHeight(so) / 2)));
		int tile2 = this.getTile(Util.newPoint(Util.getX(so) - 1 + (Util.getWidth(so) / 2), Util.getY(so) - (Util.getHeight(so) / 2)));
		int tile3 = this.getTile(Util.newPoint(Util.getX(so) - (Util.getWidth(so) / 2), (Util.getY(so) - 1) + (Util.getHeight(so) / 2)));
		int rangeW = tile2 - tile1 + 1;
		int rangeH = ((tile3 - tile1) / this.noTilesW) + 1;

		for (int m = 0; m < rangeH; m++) {
			for (int n = 0; n < rangeW; n++) {
				int tile = n + (m * this.noTilesW) + tile1;
				this.obstacles[tile] = this.obstacles[tile] - type;
			}
		}
	}

	/**
	 * Adiciona um obstaculo comum - Obstacle, Resource e Building
	 * 
	 * @param so
	 *            - Obstaculo a ser adicionado
	 */
	public void addObstacle(Long so, int type) {
		int tile1 = this.getTile(Util.newPoint(Util.getX(so) - (Util.getWidth(so) / 2), Util.getY(so) - (Util.getHeight(so) / 2)));
		int tile2 = this.getTile(Util.newPoint(Util.getX(so) - 1 + (Util.getWidth(so) / 2), Util.getY(so) - (Util.getHeight(so) / 2)));
		int tile3 = this.getTile(Util.newPoint(Util.getX(so) - (Util.getWidth(so) / 2), (Util.getY(so) - 1) + (Util.getHeight(so) / 2)));
		int rangeW = tile2 - tile1 + 1;
		int rangeH = ((tile3 - tile1) / this.noTilesW) + 1;

		for (int m = 0; m < rangeH; m++) {
			for (int n = 0; n < rangeW; n++) {
				int tile = n + (m * this.noTilesW) + tile1;
				this.obstacles[tile] = this.obstacles[tile] + type;
			}
		}
	}

	public List<Integer> getAvailableNeighborTiles(int point) {
		List<Integer> ret;

		int tile = getTile(point);
		ret = new ArrayList<Integer>();
		List<Integer> neighbors = get4Neighbors(tile);

		for (Integer neighbor : neighbors) {
			if (isAvailable(neighbor)) {
				ret.add(neighbor);
			}
		}

		return ret;
	}

	public boolean isAvailable(int tile) {
		boolean ret;

		ret = obstacles[tile] == 0;

		return ret;
	}

	public List<Long> getMineAsObstacles(Resource res) {
		List<Long> ret;

		ret = new ArrayList<Long>();

		int tile = getTile(Util.newPoint(res.getX(), res.getY()));
		int w = res.getWidth();
		int h = res.getHeight();
		int size = Util.newSize(w, h);

		boolean hasUp = tile - this.noTilesW >= 0;
		boolean has2Up = tile - 2 * this.noTilesW >= 0;

		boolean hasDown = tile + this.noTilesW < this.totalTiles;
		boolean has2Down = tile + 2 * this.noTilesW < this.totalTiles;

		boolean hasLeft = tile % this.noTilesW != 0;
		boolean has2Left = hasLeft && (tile - 1) % this.noTilesW != 0;

		boolean hasRight = (tile + 1) % this.noTilesW != 0;
		boolean has2Right = hasRight && (tile + 2) % this.noTilesW != 0;

		Integer p;
		if (hasUp) {
			p = this.getCentral(tile - this.noTilesW);
			ret.add(Util.newStaticObstacle(p, size));

			if (has2Up) {
				p = this.getCentral(tile - 2 * this.noTilesW);
				ret.add(Util.newStaticObstacle(p, size));
			}
		}

		if (hasDown) {
			p = this.getCentral(tile + this.noTilesW);
			ret.add(Util.newStaticObstacle(p, size));

			if (has2Down) {
				p = this.getCentral(tile + 2 * this.noTilesW);
				ret.add(Util.newStaticObstacle(p, size));
			}
		}

		if (hasLeft) {
			p = getCentral(tile - 1);
			ret.add(Util.newStaticObstacle(p, size));

			if (has2Left) {
				p = this.getCentral(tile - 2);
				ret.add(Util.newStaticObstacle(p, size));
			}
		}

		if (hasRight) {
			p = getCentral(tile + 1);
			ret.add(Util.newStaticObstacle(p, size));

			if (has2Right) {
				p = this.getCentral(tile + 2);
				ret.add(Util.newStaticObstacle(p, size));
			}
		}

		if (hasUp && hasLeft) {
			p = getCentral(tile - this.noTilesW - 1);
			ret.add(Util.newStaticObstacle(p, size));
		}

		if (hasUp && hasRight) {
			p = getCentral(tile - this.noTilesW + 1);
			ret.add(Util.newStaticObstacle(p, size));
		}

		if (hasDown && hasLeft) {
			p = getCentral(tile + this.noTilesW - 1);
			ret.add(Util.newStaticObstacle(p, size));
		}

		if (hasDown && hasRight) {
			p = getCentral(tile + this.noTilesW + 1);
			ret.add(Util.newStaticObstacle(p, size));
		}

		p = getCentral(tile);
		ret.add(Util.newStaticObstacle(p, size));

		return ret;
	}

}
